import dayjs from "dayjs";
import { PlatformClient } from "../platformClient";
import { downloadFileLocal, getPathInfo } from "../../helper";
import { File } from "./file";
import { Uploader } from "./uploader";
import { PlatformUserAccountExpiredError } from "../../errors";
import { v4 as uuidv4 } from "uuid";

export class Client extends PlatformClient {
  static BASE_URL = 'https://channels.weixin.qq.com'
  static REFERER = 'https://channels.weixin.qq.com/platform/post/create'

  wechatUin = '0000000000'

  constructor(accountUser) {
    super()
    this.accountUser = accountUser;
  }

  async getUserInfo(finderUsername = '') {
    if (finderUsername) {
      this.accountUser.user.third_return = Object.assign({}, this.accountUser.user.third_return, { finderUsername })
    }

    return this.getAuthData()
  }

  getAuthData(){
    return this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/auth/auth_data',
      data: this.formatBody()
    })
  }

  async uploadFile(urls){
    urls = [].concat(urls)
    let filePaths = await Promise.all(urls.map(url => downloadFileLocal(url)))
    filePaths = filePaths.filter(filePath => !!filePath)

    let { data: authResponse } = await this.getAuthData()

    if(authResponse.errCode != 0){
      throw new PlatformUserAccountExpiredError(authResponse.errMsg)
    }

    let envInfo = authResponse.data.envInfo || {}
    let { data: uploadResponse } = await this.getUploadParams()

    const uploader = new Uploader(uploadResponse, envInfo);

    if (envInfo.cdnHost) {
        uploader.setHost('https://' + envInfo.cdnHost);
    }

    const CONCURRENT_UPLOADS = 3; // 最大并发数
    const results = [];
    
    for (let i = 0; i < filePaths.length; i += CONCURRENT_UPLOADS) {
      const batch = filePaths.slice(i, i + CONCURRENT_UPLOADS);
      const batchPromises = batch.map(async (filePath) => {
        try {
          const result = await uploader.upload(new File(filePath));

          return {
            filePath,
            ...result,
            success: true
          };
        } catch (error) {
          console.error(`上传视频失败: ${filePath}`, error);
          return {
            filePath,
            error: error.message,
            success: false
          };
        }
      });

      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }

    // 统计上传结果
    const successCount = results.filter(r => r.success).length;
    console.log(`上传完成: 成功 ${successCount}/${filePaths.length}`);

    // 返回成功的结果
    return results.filter(r => r.success);
  }

  async uploadVideo(params){
    if (!params.file) {
      throw new Error('file不能为空');
    }

    const title = params.title || '';
    const component = params.component;
    const member = params.member;
    const effectiveTime = params.effectiveTime || 0;
    const hashtagName = params.hashtag_name || [];
    const topics = hashtagName.length ? hashtagName : [];
    const duration = params.duration || '';
    const coverUrl = params.coverUrl || '';

    let desc = params.desc || '';
    let formatHeight = params.formatHeight || '';
    let formatWidth = params.formatWidth || '';
    let filesize = params.fileSize;

    const traceKeyResult = await this.getFinderPostTraceKey();
    const [ videoResult = null ] = await this.uploadFile(params.file);

    if (!videoResult) {
      throw new Error('微信视频上传失败');
    }

    const traceInfo = {
      ...videoResult.traceInfo,
      traceKey: traceKeyResult.data.traceKey,
    }

    const clipResult = await this.postClipVideo({
      url: videoResult.httpsUrl,
      timeStart: 0,
      cropDuration: 0,
      height: formatHeight,
      width: formatWidth,
      x: 0,
      y: 0,
      clipOriginVideoInfo: {
        width: formatWidth,
        height: formatHeight,
        duration: duration,
        fileSize: filesize || videoResult.size,
      },
      traceInfo: traceInfo,
      ...this.calcTargetSize(formatWidth, formatHeight)
    });

    let corpVideoUrl = '';
    let videoPlayLen = parseFloat(duration);
    let clipRequestLimit = 1;
    let clipVideoResultMd5sum = '';

    while (!corpVideoUrl && clipRequestLimit < 60) {
      const response = await this.postClipVideoResult({
        clipKey: clipResult.data.clipKey,
        draftId: clipResult.data.draftId
      });

      const flag = response.data?.flag ?? 2;

      if (flag === 1) {
        corpVideoUrl = response.data.url;
        videoPlayLen = response.data.duration;
        filesize = response.data.fileSize;
        formatWidth = response.data.width;
        formatHeight = response.data.height;
        clipVideoResultMd5sum = response.data.md5;
        break;
      }

      await new Promise(resolve => setTimeout(resolve, this.retryGetMediaInfoUsleep(clipRequestLimit))); // 替代 usleep
      
      clipRequestLimit++;
    }

    if (!corpVideoUrl) {
      throw new Error('微信视频发表失败');
    }

    const clipVideoResultUrlParams = getPathInfo(corpVideoUrl)

    const [ coverResult = null ] = await this.uploadFile(coverUrl);

    if (! coverResult) {
      throw new Error('微信封面上传失败');
    }

    const finderCoverUrl = coverResult.httpsUrl;

    let finderTopicInfo = '';
    let valuecount = topics.length;
    let valKey = 0;
    if (desc) {
      valuecount = topics.length + 1;
      finderTopicInfo = `<finder><version>1</version><valuecount>${valuecount}</valuecount><style><at></at></style><value0><![CDATA[${desc}]]></value0>`;
      valKey = 1;
    } else if (topics.length) {
      finderTopicInfo = `<finder><version>1</version><valuecount>${valuecount}</valuecount><style><at></at></style>`;
      valKey = 0;
    }

     if (topics.length) {
      for (const topic of topics) {
        finderTopicInfo += `<value${valKey}><topic><![CDATA[#${topic}#]]></topic></value${valKey}>`;
        desc += `#${topic}`;
        valKey++;
      }
    }

    if (finderTopicInfo) {
      finderTopicInfo += '</finder>';
    }

    const objectDesc = {
      mpTitle: '',
      description: desc,
      extReading: {},
      mediaType: 4,
      location: {},
      topic: { finderTopicInfo },
      event: {},
      mentionedUser: [],
      media: [{
        url: corpVideoUrl,
        fileSize: filesize,
        thumbUrl: finderCoverUrl,
        fullThumbUrl: finderCoverUrl,
        mediaType: 4,
        videoPlayLen,
        width: formatWidth,
        height: formatHeight,
        md5sum: uuidv4(),
        coverUrl: finderCoverUrl,
        fullCoverUrl: finderCoverUrl,
        urlCdnTaskId: clipResult.data.draftId,
      }],
    }

    if (title) {
      objectDesc.shortTitle = [{ shortTitle: title }];
    }

    if (component) {
      objectDesc.component = component;
    }
    
    objectDesc.member = member || {};

    const report = {
      clipKey: clipResult.data.clipKey,
      draftId: clipResult.data.draftId,
      height: formatHeight,
      width: formatWidth,
      duration: videoPlayLen,
      fileSize: filesize,
      uploadCost: traceInfo.uploadCdnEnd - traceInfo.uploadCdnStart,
    }

    const createParams = {
      objectType: 0,
      longitude: 0,
      latitude: 0,
      feedLongitude: 0,
      feedLatitude: 0,
      originalFlag: 0,
      topics,
      isFullPost: 1,
      handleFlag: 2,
      videoClipTaskId: clipResult.data.draftId,
      traceInfo,
      objectDesc,
      report,
      postFlag: 0,
      mode: 1,
      clientid: uuidv4(),
      timestamp: dayjs().valueOf(),
    }

    if (effectiveTime > 0) {
      createParams.effectiveTime = effectiveTime;
    }

    let createResult = await this.postCreate(createParams);
    
    createResult.clipVideoResultMd5sum = clipVideoResultMd5sum;
    createResult.clipVideoResultUrlParams = clipVideoResultUrlParams;
    createResult.createParams = createParams

    // 等待15秒
    await new Promise(resolve => setTimeout(resolve, 15000));

    const videoMedia = await this.getVideoStatus(clipVideoResultUrlParams.query.encfilekey || '', clipVideoResultMd5sum)
    
    createResult.videoMedia = videoMedia

    return {
      aweme: createResult
    }
  }

  async getVideoStatus(encfilekey, clipVideoResultMd5sum) {
    const now = dayjs().unix();
    const resultParams = {
      startTime: now - 15,
      endTime: now + 15
    };

    let result = await this.getPostList('', 0, resultParams);
    console.info('weixinshipin==uploadVideo>>getStatisticPostList', { result });

    // 如果列表为空，尝试使用另一个接口获取
    if (!result.data?.list?.length) {
      result = await this.getStatisticPostList('', 0, resultParams);
    }

    let videoMedia = null;

    // 遍历列表查找匹配的视频
    for (const itemValue of (result.data?.list || [])) {
      const media = itemValue.desc?.media || [];
      
      // 通过 md5 匹配
      if (media.some(m => m.md5sum === clipVideoResultMd5sum)) {
        videoMedia = itemValue;
        break;
      }

      // 通过 encfilekey 匹配
      if (encfilekey && media.some(m => m.url?.includes(encfilekey))) {
        videoMedia = itemValue;
        break;
      }
    }

    return videoMedia;
  }

  async getPostList(params) {
    const now = dayjs().unix();

    const data = {
      pageSize: params.pageSize || 5,
      currentPage: params.currentPage || 1,
      userpageType: 11,
      sort: params.sort || 0,
      order: params.order || 0,
      startTime: params.startTime || now - 10,
      endTime: params.endTime || now + 10,
    };

    const headers = {};

    if (params.fingerPrintDeviceId) {
      headers['finger-print-device-id'] = params.fingerPrintDeviceId;
    }

    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/post/post_list',
      data: {
        ...this.formatBody(),
        ...data
      },
      headers
    });

    if (response.errCode !== 0) {
      throw new Error(response.errMsg);
    }

    return response;
  }

  async getStatisticPostList(params) {
    const now = dayjs().unix();

    const data = {
      pageSize: params.pageSize || 10,
      currentPage: params.currentPage || 1,
      sort: params.sort || 0,
      order: params.order || 0,
      startTime: params.startTime || now - 10,
      endTime: params.endTime || now + 10,
    };

    const headers = {};
    if (params.fingerPrintDeviceId) {
      headers['finger-print-device-id'] = params.fingerPrintDeviceId;
    }

    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/statistic/post_list',
      data: {
        ...this.formatBody(),
        ...data
      },
      headers
    });

    if (response.errCode !== 0) {
      throw new Error(response.errMsg);
    }

    return response;
  }

  async postCreate(params){
    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/post/post_create',
      data: {
       ...this.formatBody(),
       ...params
      }
    })

    if (response.errCode!= 0) {
      throw new Error(response.errMsg)
    }

    return response
  }

  async getUploadParams() {
    let { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/helper/helper_upload_params',
      data: this.formatBody()
    })

    if (response.errCode != 0) {
      throw new Error(response.errMsg)
    }

    if (response.data.uin) {
      this.wechatUin = response.data.uin;
    }

    return response
  }

  calcTargetSize(width, height) {
    const result = {};
    
    const maxSide = Math.max(width, height);
    const minSide = Math.min(width, height);
    const maxWidth = 1920;
    const maxHeight = 1080;

    if (maxSide > maxWidth || minSide > maxHeight) {
      const ratio = (maxSide / minSide > maxWidth / maxHeight) 
        ? (maxWidth / maxSide) 
        : (maxHeight / minSide);
      
      const scaledMax = Math.floor(maxSide * ratio);
      const scaledMin = Math.floor(minSide * ratio);
      
      result.targetWidth = width > height ? scaledMax : scaledMin;
      result.targetHeight = width > height ? scaledMin : scaledMax;
    } else {
      result.targetWidth = width;
      result.targetHeight = height;
    }

    return result;
  }

  async getFinderPostTraceKey(){
    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/post/get-finder-post-trace-key',
      data: {
        ...this.formatBody(),
        objectId: ''
      }
    })

    if (response.errCode != 0) {
      throw new Error(response.errMsg)
    }

    return response
  }

  retryGetMediaInfoUsleep(key = -1){
    let usleepArray = [
        2000, 3000, 3000, 5000, 5000, 8000, 10000, 15000, 15000,
    ];

    if (key > -1) {
        return usleepArray[key] || 5000;
    }
    return usleepArray;
  }

  async postClipVideo(params) {
    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/post/post_clip_video',
      data: {
        ...this.formatBody(),
        ...params
      }
    })

    if (response.errCode!= 0) {
      throw new Error(response.errMsg)
    }

    return response
  }

  async postClipVideoResult(params){
    const { data: response } = await this.request({
      method: 'POST',
      url: '/cgi-bin/mmfinderassistant-bin/post/post_clip_video_result',
      data: {
        ...this.formatBody(),
        ...params
      }
    })

    if (response.errCode!= 0) {
      throw new Error(response.errMsg)
    }

    return response
  }

  formatBody(){
    return {
      pluginSessionId: null,
      rawKeyBuff: null,
      reqScene: 7,
      scene: 7,
      timestamp: dayjs().valueOf(),
      _log_finder_id: this.accountUser.getThirdRuturn('finderUsername'),
      _log_finder_uin: '',
    }
  }

  generateUniqueIdentifier() {
    const timestamp = Math.floor(Date.now() / 1e3).toString(16);
    const random = [...Array(8)].map(() => Math.floor(16 * Math.random()).toString(16)).join('');
    return `${timestamp}-${random}`;
  }

  async request(options){
    options['method'] = options['method'] || 'GET'

    options['headers'] = Object.assign({}, {
      'Cookie': this.accountUser.cookie,
      'User-Agent': this.accountUser.userAgent,
      'Referer': Client.REFERER,
      'X-Wechat-Uin': this.wechatUin,
    }, options['headers'] || {})

    options['params'] = Object.assign({}, options['params'] || {}, {
      _rid: this.generateUniqueIdentifier(),
    })

    let response

    try {
      response = await this.clientRequest.request({
        baseURL: Client.BASE_URL,
       ...options
      })
    } catch (e){
      console.log('视频号请求报错', options)
      throw e
    }

    return response
  }
}